﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ICTCLAS;
using System.IO;

namespace RelationExtraction
{
    public class KeyRule
    {
        public string key;
        public int d;
        public KeyRule(String keys, int dis)
        {
            key = keys;
            d = dis;
        }
        public KeyRule(String context)
        {
            String[] a = context.Split('$');
            key = a[0];
            d = int.Parse(a[1]);
        }
        public int count = 0;
        public static int scount = 0;
        public static int unscount = 0;
    }
    public class Rule
    {
        public const string A = "A";
        public const string B = "B";
        public int weight { get; set; }
        public List<string> Tokens { get; set; }


        public Rule(string tokenString)
        {
            Tokens = new List<string>(tokenString.Split('$'));
        }

        public override string ToString()
        {
            string temp = "";
            foreach (string token in Tokens)
            {
                temp += token + " ";
            }
            return temp;
        }
    }

    public class VoteString
    {
        public VoteString(String a, int n)
        {
            name = a;
            this.n = n;
        }
        public int i;
        public String name;
        public int n = 0;
    }

    public class Extracts
    {
        public string Name;
        public int Count { get { return Evidences.Count; } }
        public List<string> Evidences;

        public Extracts(string name)
        {
            Name = name;
            Evidences = new List<string>(5);
        }
    }

    public class Extraction
    {
        public static HashSet<string> listSpliter;

        public static void Extract(int type, Dictionary<string, List<Entity>> map1, Dictionary<string, List<Entity>> map2, List<Rule> rules, List<KeyRule> keyRules, string outputFile, string relationName)
        {
            int allCount = 0;
            int extractCount = 0;
            int validCount = 0;
            int identicalCount = 0;

            if (!Directory.Exists("resultdata"))
            {
                Directory.CreateDirectory("resultdata");
            }
            if (!Directory.Exists("结果"))
            {
                Directory.CreateDirectory("结果");
            }
            StreamWriter sw = new StreamWriter("resultdata\\" + outputFile);
            StreamWriter sw1 = new StreamWriter("结果\\" + relationName + ".txt");

            foreach (KeyValuePair<string, List<Entity>> item in map1)
            {
                foreach (Entity entity in item.Value)
                {
                    Dictionary<string, Extracts> extractedMap = new Dictionary<string, Extracts>(5);
                    List<Extracts> extractedList = new List<Extracts>(2);

                    allCount++;
                    bool simpleCase = false;
                    //special case
                    if (type == 1 && entity.Description.StartsWith("歌手："))
                    {
                        string singer = entity.Description.Substring(3).Trim();
                        if (map2.ContainsKey(singer))
                        {
                            simpleCase = true;
                            Extracts extracts = new Extracts(singer);
                            extracts.Evidences.Add("[歌手：]" + "【" + singer + "】");
                            validCount++;
                            extractedList.Add(extracts);
                        }
                    }
                    if (simpleCase == false)
                    {
                        //method 1
                        List<WordSegmentResultItem> words = WordSegment.Process(entity.Description);
                        foreach (Rule rule in rules)
                        {
                            int start = 0;
                            while (start < words.Count)
                            {
                                int[] matches;
                                //extract
                                List<string> extractedList1 = ExtractOneEntityBySplitWord(entity, words, ref start, rule, out matches);
                                if (extractedList1 == null)
                                {
                                    continue;
                                }
                                //validiate
                                foreach (string extracted in extractedList1)
                                {
                                    if (extracted != null && !string.IsNullOrEmpty(extracted))
                                    {
                                        extractCount++;
                                        if (map2.ContainsKey(extracted))
                                        {
                                            //valid
                                            if (extractedMap.ContainsKey(extracted))
                                            {
                                                extractedMap[extracted].Evidences.Add(getEvidenceString(words, matches));
                                            }
                                            else
                                            {
                                                Extracts extracts = new Extracts(extracted);
                                                extracts.Evidences.Add(getEvidenceString(words, matches));
                                                extractedMap.Add(extracted, extracts);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        //method 2
                        //extract
                        List<String> a = new List<String>();
                        List<Extracts> extractedList2 = ExtractEntity(map2, entity.Description, keyRules, a);

                        //vote
                        bool vote = true;
                        if (extractedMap.Count == 0 || extractedList2.Count == 0)
                        {
                            vote = false;
                        }
                        foreach (Extracts extract in extractedList2)
                        {
                            extractCount++;
                            if (extractedMap.ContainsKey(extract.Name))
                            {
                                extractedMap[extract.Name].Evidences.AddRange(extract.Evidences);
                            }
                            else
                            {
                                extractedMap.Add(extract.Name, extract);
                            }
                        }
                        if (vote)
                        {
                            foreach (KeyValuePair<string, Extracts> extractItem in extractedMap)
                            {
                                if (extractItem.Value.Count > 1)
                                {
                                    extractedList.Add(extractItem.Value);
                                    validCount += extractedList.Count;
                                }
                            }
                        }
                    }

                    foreach (Extracts extracts in extractedList)
                    {
                        //match
                        Entity mappedEntity = null;
                        if (map2[extracts.Name].Count > 1)
                        {
                            mappedEntity = mapEntity(type, map2, entity, extracts);
                        }
                        else
                        {
                            mappedEntity = map2[extracts.Name][0];
                        }
                        //output
                        if (mappedEntity != null)
                        {
                            string evidence = extracts.Evidences[0];
                            sw.WriteLine(entity.Id + "$" + entity.Name + "$" + mappedEntity.Id + "$" + mappedEntity.Name + "$" + evidence);
                            sw1.WriteLine(entity.Id + "-" + relationName + "-" + mappedEntity.Id);
                            identicalCount++;
                        }
                    }

                }
            }

            Console.WriteLine(allCount + "/" + extractCount + "/" + validCount + "/" + identicalCount);

            sw.Close();
            sw1.Close();
        }

        /*test the string contain chinese name or not                                              */
        public static String TestChineseName(Dictionary<String, List<Entity>> map, String test, int dir)
        {
            String name = null;
            if (test.Length == 2)
                if (map.ContainsKey(test))
                {
                    name = test;
                    return name;
                }
            if (test.Length >= 3)
            {
                for (int start = 0; start < test.Length; start++)
                    for (int namelen = 2; namelen + start <= test.Length && namelen <= 5; namelen++)
                    {
                        if (map.ContainsKey(test.Substring(start, namelen)))
                        {
                            if (namelen + 1 + start <= test.Length && map.ContainsKey(test.Substring(start, namelen + 1)))
                            {
                                name = test.Substring(start, namelen + 1);
                            }
                            else
                            {
                                name = test.Substring(start, namelen);
                            }
                            if (dir < 0) return name;
                        }
                    }
            }
            return name;
        }
        public static String TestChineseNames(Dictionary<String, List<Entity>> map, String test, int dir, List<Extracts> extractedList, String description)
        {
            if (test.Contains("、"))
            {
                String[] re = test.Split('、');

                for (int i = 0; i < re.Length; i++)
                {
                    String a = null;
                    if (i == 0) a = TestChineseName(map, re[0], 1);
                    if (i == re.Length - 1) a = TestChineseName(map, re[re.Length - 1], -1);
                    else
                    {
                        a = TestChineseName(map, re[i], 1);
                    }
                    if (a != null)
                    {
                        string evidenceString = getEvidenceString(description, description.IndexOf(re[i]), re[i].Length);
                        int k = 0;
                        for (; k < extractedList.Count; k++)
                        {
                            if (extractedList[k].Name.Equals(a))
                            {
                                extractedList[k].Evidences.Add(evidenceString);
                                break;
                            }
                        }
                        if (k >= extractedList.Count)
                        {
                            Extracts t = new Extracts(a);
                            t.Evidences.Add(evidenceString);
                            extractedList.Add(t);
                        }
                    }
                }
                return null;

            }
            else return TestChineseName(map, test, dir);
        }


        public static List<Extracts> ExtractEntity(Dictionary<String, List<Entity>> map, String description, List<KeyRule> rule, List<String> evidence)
        {
            List<Extracts> extractedList = new List<Extracts>();
            List<string> re = new List<String>();
            char[] a = { '。', '，', '!' };
            String[] word = description.Split(a);

            for (int i = 0; i < word.Length; i++)
            {
                foreach (KeyRule kRule in rule)
                {
                    String content = word[i].Substring(0, word[i].Length);
                    int loc = content.IndexOf(kRule.key);
                    while (loc >= 0)
                    {
                        if (kRule.d > 0)
                        {
                            String tname = TestChineseNames(map, content.Substring(0, loc - 0), kRule.d, extractedList, description);
                            if (tname != null)
                            {
                                string evidenceString = getEvidenceString(description, description.IndexOf(word[i]), word[i].Length);
                                int k = 0;
                                for (; k < extractedList.Count; k++)
                                {
                                    if (extractedList[k].Name.Equals(tname))
                                    {
                                        extractedList[k].Evidences.Add(evidenceString);
                                        break;
                                    }
                                }
                                if (k >= extractedList.Count)
                                {
                                    Extracts t = new Extracts(tname);
                                    t.Evidences.Add(evidenceString);
                                    extractedList.Add(t);
                                }
                            }
                        }
                        else
                        {
                            string tname = TestChineseName(map, content.Substring(loc + kRule.key.Length), kRule.d);
                            if (tname != null)
                            {
                                string evidenceString = getEvidenceString(description, description.IndexOf(word[i]), word[i].Length);
                                int k = 0;
                                for (; k < extractedList.Count; k++)
                                {
                                    if (extractedList[k].Name.Equals(tname))
                                    {
                                        extractedList[k].Evidences.Add(evidenceString);
                                        break;
                                    }
                                }
                                if (k >= extractedList.Count)
                                {
                                    Extracts t = new Extracts(tname);
                                    t.Evidences.Add(evidenceString);
                                    extractedList.Add(t);
                                }
                            }
                        }
                        content = content.Substring(loc + kRule.key.Length);
                        loc = content.IndexOf(kRule.key);
                    }
                }
            }
            return extractedList;
        }

        public static String ExtractOneEntity(Entity entity, Rule rule)
        {
            int n = rule.Tokens.Count;
            List<int> index = new List<int>();
            for (int i = 0; i < n; i++)
            {
                if (rule.Tokens[i] != Rule.A && rule.Tokens[i] != Rule.B)
                {
                    index.Add(i);
                }
            }
            foreach (int loc in index)
            {
                int newloc = entity.Description.LastIndexOf(rule.Tokens[loc]);
            }
            return null;
        }

        /*extract one Entity from all the dictionary                                                                                         */
        public static String ExtractOneEntityByDis(Dictionary<string, List<Entity>> map1, Dictionary<string, List<Entity>> map2, List<KeyRule> rules)
        {
            foreach (KeyValuePair<string, List<Entity>> item in map1)
            {
                foreach (Entity entity in item.Value)
                {
                    List<String> a = new List<String>();
                    if (ExtractEntity(map2, entity.Description, rules, a) != null)
                    {
                        using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"D:\study\grade one\modern dbs\project1\RelationExtraction\RelationExtraction\bin\Debug\entity\find.txt", true))
                        {
                            file.WriteLine(entity.Name + "    " + entity.Description);
                        }
                        KeyRule.scount++;
                    }
                    else
                    {
                        using (System.IO.StreamWriter file = new System.IO.StreamWriter(@"D:\study\grade one\modern dbs\project1\RelationExtraction\RelationExtraction\bin\Debug\entity\unfind.txt", true))
                        {
                            file.WriteLine(entity.Name + "    " + entity.Description);
                        }
                        KeyRule.unscount++;
                    }
                }
            }
            return null;
        }

        //not used now
        private static void voteForExtracted(Dictionary<string, Extracts> extractedMap, out Extracts voteExtracts, out string voteEvidence)
        {
            voteExtracts = null;
            voteEvidence = "";
            int voteNum = 0;
            if (extractedMap.Count > 1)
            {
                foreach (KeyValuePair<string, Extracts> extractItem in extractedMap)
                {
                    if (extractItem.Value.Count > voteNum)
                    {
                        voteExtracts = extractItem.Value;
                        voteNum = extractItem.Value.Count;
                    }
                }
            }
            else
            {
                foreach (KeyValuePair<string, Extracts> extractItem in extractedMap)
                {
                    voteExtracts = extractItem.Value;
                    break;
                }
            }
        }

        private static Entity mapEntity(int type, Dictionary<string, List<Entity>> entityMap, Entity entity, Extracts voteExtracts)
        {
            List<Entity> entityList = entityMap[voteExtracts.Name];
            List<int> entitySimiliar = new List<int>();
            Dictionary<string, int> entityFrequent = new Dictionary<string, int>();
            if (type == 0)
            {
                entityFrequent.Add("歌手", 5);
                entityFrequent.Add("明星", 1);
            }
            else if (type == 1)
            {
                entityFrequent.Add("作曲", 5);
            }
            else if (type == 2)
            {
                entityFrequent.Add("作词", 5);
            }
            else if (type == 3)
            {
                entityFrequent.Add("演员", 5);
                entityFrequent.Add("明星", 1);
            }
            else if (type == 4)
            {
                entityFrequent.Add("导演", 5);
            }
            getWordFrequent(entity, entityFrequent);
            foreach (Entity entity2 in entityMap[voteExtracts.Name])
            {
                Dictionary<string, int> entityFrequent2 = new Dictionary<string, int>();
                getWordFrequent(entity2, entityFrequent2);
                int similarity = getEntitySimilarity(entityFrequent, entityFrequent2);
                entitySimiliar.Add(similarity);
            }
            int max = 0;
            Entity mappedEntity = null;
            for (int i = 0; i < entityList.Count; i++)
            {
                if (entitySimiliar[i] > max)
                {
                    max = entitySimiliar[i];
                    mappedEntity = entityList[i];
                }
            }
            return mappedEntity;
        }

        private static int getEntitySimilarity(Dictionary<string, int> entityFrequent, Dictionary<string, int> entityFrequent2)
        {
            int frequent = 0;
            foreach (KeyValuePair<string, int> item in entityFrequent)
            {
                if (entityFrequent2.ContainsKey(item.Key))
                {
                    frequent += entityFrequent2[item.Key] * item.Value;
                }
            }
            return frequent;
        }

        private static void getWordFrequent(Entity entity, Dictionary<string, int> entityFrequent)
        {
            List<WordSegmentResultItem> segmentResults = WordSegment.Process(entity.Description);
            foreach (WordSegmentResultItem item in segmentResults)
            {
                if (Common.functionWordsHash.Contains(item.Word) == false)
                {
                    if (entityFrequent.ContainsKey(item.Word))
                    {
                        entityFrequent[item.Word]++;
                    }
                    else
                    {
                        entityFrequent.Add(item.Word, 1);
                    }
                }
            }
        }

        private static string getEvidenceString(List<WordSegmentResultItem> words, int[] matches)
        {
            string evidence = "";
            for (int i = 0; i < words.Count; i++)
            {
                if (matches[i] == 1)
                {
                    evidence += "[" + words[i].Word + "]";
                }
                else if (matches[i] == 2)
                {
                    evidence += "【" + words[i].Word + "】";
                }
                else
                {
                    evidence += words[i].Word;
                }
            }
            return evidence;
        }
        private static string getEvidenceString(String description, int start, int end)
        {
            string evidence = "";
            evidence = "[" + description.Substring(start, end) + "]";
            return evidence;
        }

        public static List<string> ExtractOneEntityBySplitWord(Entity entity, List<WordSegmentResultItem> words, ref int start, Rule rule, out int[] matchresult)
        {
            int nowend = start;
            List<string> extractedList = new List<string>(5);
            int count = rule.Tokens.Count;
            int[] matches = new int[count];
            for (int i = 0; i < count; i++)
            {
                matches[i] = -2;//not matched
            }
            matchresult = new int[words.Count];
            for (int i = 0; i < words.Count; i++)
            {
                matchresult[i] = 0;
            }
            int j = start;
            for (int i = 0; i < count; i++)
            {
                if (rule.Tokens[i].Equals(Rule.B))
                {
                    matches[i] = -1;//if B
                    if (i == count - 1)
                    {
                        j++;//match one word
                    }
                    continue;
                }

                while (j < words.Count)
                {
                    //match A
                    if (rule.Tokens[i].Equals(Rule.A) && words[j].Word.Equals(entity.Name))
                    {
                        matches[i] = j;
                        j++;
                        break;
                    }
                    else if (rule.Tokens[i].Equals(words[j].Word))//match other word
                    {
                        matches[i] = j;
                        j++;
                        break;
                    }
                    j++;
                }
            }
            nowend = j;
            for (int i = 0; i < count; i++)
            {
                if (matches[i] == -2)
                {
                    start = nowend;
                    return null;
                }
            }
            int k = 0;
            while (k < count)
            {
                if (matches[k] != -1) //not B
                {
                    if (matches[k] >= 0 && matches[k] < words.Count)
                    {
                        matchresult[matches[k]] = 1;
                    }
                }
                else //B
                {
                    if (k == 0)//B is first
                    {
                        if (matches[k + 1] > 0)
                        {
                            int matchlast = matches[k + 1] - 1;
                            for (int t = matchlast; t > start; )
                            {
                                if (t > start && listSpliter.Contains(words[t - 1].Word)) //是分开多个人名的
                                {
                                    extractedList.Add(words[t].Word);
                                    matchresult[t] = 2;
                                    matchresult[t - 1] = 1;
                                    t -= 2;
                                }
                                else
                                {
                                    extractedList.Add(words[t].Word);
                                    matchresult[t] = 2;
                                    break;
                                }
                            }
                        }
                    }
                    else if (matches[k - 1] < words.Count - 1)
                    {
                        int findend = words.Count - 1;
                        if (k != count - 1)//B is not last
                        {
                            findend = matches[k + 1] - 1;
                        }
                        int matchfirst = matches[k - 1] + 1;
                        for (int t = matchfirst; t < findend; )
                        {
                            if (t < findend && listSpliter.Contains(words[t + 1].Word))//是分开多个人名的
                            {
                                extractedList.Add(words[t].Word);
                                matchresult[t] = 2;
                                matchresult[t + 1] = 1;
                                t += 2;
                            }
                            else
                            {
                                extractedList.Add(words[t].Word);
                                matchresult[t] = 2;
                                break;
                            }
                        }
                    }
                }
                k++;
            }
            start = nowend;
            return extractedList;
        }
    }
}
